#include "Glsl.h"


#include <fstream>
using std::ifstream;
using std::ios;

#include <sstream>
using std::ostringstream;

#include <sys/stat.h>

Glsl::Glsl () : handle (0), linked (false) {}

Glsl::Glsl (char *vert, char *geom, char *frag) : handle (0), linked (false)
{
   if (!compileShaderFile (vert, GlslShader::VERTEX)) {
      printf ("Vertex shader failed to compile!\n%s",
         log ().c_str ());
      //exit (1);
   }
   if (geom != NULL && !compileShaderFile (geom, GlslShader::GEOMETRY)) {
      printf ("Geometry shader failed to compile!\n%s",
         log ().c_str ());
      //exit (1);
   }
   if (!compileShaderFile (frag, GlslShader::FRAGMENT)) {
      printf ("Fragment shader failed to compile!\n%s",
      log ().c_str ());
      //exit (1);
   }
}

bool Glsl::compileShaderFile (const char * fileName, GlslShader::GlslShaderType type)
{
   if (!fileExists (fileName)) {
      logString = "File not found.";
      return false;
   }

   if (handle <= 0) {
      handle = glCreateProgram ();
      if (handle == 0) {
         logString = "Unable to create shader program.";
         return false;
      }
   }

   ifstream inFile (fileName, ios::in);
   if (!inFile) {
      return false;
   }

   ostringstream code;
   while (inFile.good ()) {
      int c = inFile.get ();
      if (!inFile.eof ()) code << (char)c;
   }
   inFile.close ();

   return compileShaderString (code.str (), type);
}

bool Glsl::compileShaderString (const string & source, GlslShader::GlslShaderType type)
{
   if (handle <= 0) {
      handle = glCreateProgram ();
      if (handle == 0) {
         logString = "Unable to create shader program.";
         return false;
      }
   }

   GLuint shaderHandle = 0;

   switch (type) {
      case GlslShader::VERTEX:
         shaderHandle = glCreateShader (GL_VERTEX_SHADER);
         break;
      case GlslShader::FRAGMENT:
         shaderHandle = glCreateShader (GL_FRAGMENT_SHADER);
         break;
      case GlslShader::GEOMETRY:
         shaderHandle = glCreateShader (GL_GEOMETRY_SHADER);
         break;
      case GlslShader::TESS_CONTROL:
         shaderHandle = glCreateShader (GL_TESS_CONTROL_SHADER);
         break;
      case GlslShader::TESS_EVALUATION:
         shaderHandle = glCreateShader (GL_TESS_EVALUATION_SHADER);
         break;
      default:
         return false;
   }

   const char * c_code = source.c_str ();
   glShaderSource (shaderHandle, 1, &c_code, NULL);

   // Compile the shader
   glCompileShader (shaderHandle);

   // Check for errors
   int result;
   glGetShaderiv (shaderHandle, GL_COMPILE_STATUS, &result);
   if (GL_FALSE == result) {
      // Compile failed, store log and return false
      int length = 0;
      logString = "";
      glGetShaderiv (shaderHandle, GL_INFO_LOG_LENGTH, &length);
      if (length > 0) {
         char * c_log = new char[length];
         int written = 0;
         glGetShaderInfoLog (shaderHandle, length, &written, c_log);
         logString = c_log;
         delete[] c_log;
      }

      return false;
   }
   else {
      // Compile succeeded, attach shader and return true
      glAttachShader (handle, shaderHandle);
      printShaderInfoLog (shaderHandle);
      return true;
   }

}

bool Glsl::link ()
{
   if (linked) return true;
   if (handle <= 0) return false;

   glLinkProgram (handle);

   int status = 0;
   glGetProgramiv (handle, GL_LINK_STATUS, &status);
   if (GL_FALSE == status) {
      // Store log and return false
      int length = 0;
      logString = "";

      glGetProgramiv (handle, GL_INFO_LOG_LENGTH, &length);

      if (length > 0) {
         char * c_log = new char[length];
         int written = 0;
         glGetProgramInfoLog (handle, length, &written, c_log);
         logString = c_log;
         delete[] c_log;
      }

      return false;
   }
   else {
      std::cout << log () << std::endl;
      printf ("\nShader Output: %s\n", log ().c_str ());
      printProgramInfoLog (handle);
      printActiveAttribs ();
      printActiveUniforms ();
      linked = true;
      return linked;
   }

}

void Glsl::useProgram (bool isActive)
{
   if (handle <= 0 || (!linked)) return;
   if (isActive == true)
      glUseProgram (handle);
   else
      glUseProgram (0);
}

string Glsl::log ()
{
   return logString;
}

int Glsl::getHandle ()
{
   return handle;
}

bool Glsl::isLinked ()
{
   return linked;
}

void Glsl::bindAttribLocation (GLuint location, const char * name)
{
   glBindAttribLocation (handle, location, name);
}

void Glsl::bindFragDataLocation (GLuint location, const char * name)
{
   glBindFragDataLocation (handle, location, name);
}

void Glsl::setUniform (const char *name, float x, float y, float z)
{
   int loc = getUniformLocation (name);
   if (loc >= 0) {
      glUniform3f (loc, x, y, z);
   }
   else {
      //printf ("Uniform %s not found.\n", name);
      //system ("pause");
   }
}

void Glsl::setUniform (const char *name, const vec3 & v)
{
   this->setUniform (name, v.x, v.y, v.z);
}

void Glsl::setUniform (const char *name, const vec4 & v)
{
   int loc = getUniformLocation (name);
   if (loc >= 0) {
      glUniform4f (loc, v.x, v.y, v.z, v.w);
   }
   else {
      //printf ("Uniform %s not found.\n", name);
      //system ("pause");
   }
}

void Glsl::setUniform (const char *name, const int numVertices, vec3 *v)
{
   int loc = getUniformLocation (name);
   if (loc >= 0) {
      glUniform3fv (loc, numVertices, value_ptr(v[0]));
   }
   else {
      //printf ("Uniform %s not found.\n", name);
      //system ("pause");
   }
}
void Glsl::setUniform (const char *name, const mat4 & m)
{
   int loc = getUniformLocation (name);
   if (loc >= 0) {
      glUniformMatrix4fv (loc, 1, GL_FALSE, &m[0][0]);
   }
   else {
      //printf ("Uniform %s not found.\n", name);
      //system ("pause");
   }
}

void Glsl::setUniform (const char *name, const mat3 & m)
{
   int loc = getUniformLocation (name);
   if (loc >= 0) {
      glUniformMatrix3fv (loc, 1, GL_FALSE, &m[0][0]);
   }
   else {
      //printf ("Uniform %s not found.\n", name);
      //system ("pause");
   }
}

void Glsl::setUniform (const char *name, float val)
{
   int loc = getUniformLocation (name);
   if (loc >= 0) {
      glUniform1f (loc, val);
   }
   else {
      //printf ("Uniform %s not found.\n", name);
      //system ("pause");
   }
}

void Glsl::setUniform (const char *name, int val)
{
   int loc = getUniformLocation (name);
   if (loc >= 0) {
      glUniform1i (loc, val);
   }
   else {
      //printf ("Uniform %s not found.\n", name);
      //system ("pause");
   }
}

void Glsl::setUniform (const char *name, bool val)
{
   int loc = getUniformLocation (name);
   if (loc >= 0) {
      glUniform1i (loc, val);
   }
   else {
      //printf ("Uniform %s not found.\n", name);
      //system ("pause");
   }
}

void Glsl::setUniformBlock (glm::mat4 modelView, glm::mat4 normalMatrix, glm::mat4 projection)
{
   static const GLchar* uniformNames[1] = {
      "TransformBlock.projection"
   };
   GLuint uniformIndices[1];
   glGetUniformIndices (handle, 1, uniformNames, uniformIndices);


   printf ("\nTransformBlock.projection %d\n", uniformIndices[0]);

   GLint uniformOffsets[1]; //float, vec234, etc
   GLint arrayStrides[1];   //TYPE[]
   GLint matrixStrides[1];  //mat234

   glGetActiveUniformsiv (handle, 1, uniformIndices, GL_UNIFORM_OFFSET, uniformOffsets);
   glGetActiveUniformsiv (handle, 1, uniformIndices, GL_UNIFORM_ARRAY_STRIDE, arrayStrides);
   glGetActiveUniformsiv (handle, 1, uniformIndices, GL_UNIFORM_MATRIX_STRIDE, matrixStrides);

   printf ("\nuniformOffsets[0] = %d\narrayStrides[0] = %d\nmatrixStrides[0] = %d", uniformOffsets[0], arrayStrides[0], matrixStrides[0]);

   
   GLuint bufferIndex;
   glGenBuffers (1, &bufferIndex);
   glBindBuffer (GL_UNIFORM_BUFFER, bufferIndex); //@TODO - Question: Why super bible doesn't mention it and red book does?
   GLuint blockIndex = glGetUniformBlockIndex (handle, "TransformBlock");
   GLint blockSize;
   glGetActiveUniformBlockiv (handle, blockIndex,GL_UNIFORM_BLOCK_DATA_SIZE, &blockSize);
   
   printf ("\nBlockIndex = %d  BlockSize = %d\n", blockIndex, blockSize);
   
   unsigned char *buffer = (unsigned char*)malloc (blockSize);
   for (int i = 0; i < 4; i++) {
      GLuint offset = uniformOffsets[0] + matrixStrides[0] * i;
      for (int j = 0; j < 4; j++) {
         *((float*)(buffer + offset)) = projection[i][j];
         offset += sizeof(GLfloat);
      }
   }

   glBufferData (GL_UNIFORM_BUFFER, blockSize, buffer, GL_STATIC_DRAW);
   glBindBufferBase (GL_UNIFORM_BUFFER, 0, bufferIndex);

   free (buffer);
}

void Glsl::printActiveUniforms ()
{

   GLint nUniforms, size, location, maxLen;
   GLchar * name;
   GLsizei written;
   GLenum type;

   glGetProgramiv (handle, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxLen);
   glGetProgramiv (handle, GL_ACTIVE_UNIFORMS, &nUniforms);

   name = (GLchar *)malloc (maxLen);

   printf ("\n Location | Name\n");
   printf ("------------------------------------------------\n");
   for (int i = 0; i < nUniforms; ++i) {
      glGetActiveUniform (handle, i, maxLen, &written, &size, &type, name);
      location = glGetUniformLocation (handle, name);
      printf (" %-8d | %s\n", location, name);
   }

   free (name);
}

void Glsl::printActiveAttribs ()
{

   GLint written, size, location, maxLength, nAttribs;
   GLenum type;
   GLchar * name;

   glGetProgramiv (handle, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &maxLength);
   glGetProgramiv (handle, GL_ACTIVE_ATTRIBUTES, &nAttribs);

   name = (GLchar *)malloc (maxLength);

   printf (" Index | Name\n");
   printf ("------------------------------------------------\n");
   for (int i = 0; i < nAttribs; i++) {
      glGetActiveAttrib (handle, i, maxLength, &written, &size, &type, name);
      location = glGetAttribLocation (handle, name);
      printf (" %-5d | %s\n", location, name);
   }

   free (name);
}

int Glsl::getUniformLocation (const char * name)
{
   return glGetUniformLocation (handle, name);
}

bool Glsl::fileExists (const string & fileName)
{
   struct stat info;
   int ret = -1;

   ret = stat (fileName.c_str (), &info);
   return 0 == ret;
}

void Glsl::printShaderInfoLog (GLuint obj)
{
   int infologLength = 0;
   int charsWritten = 0;
   char *infoLog;

   glGetShaderiv (obj, GL_INFO_LOG_LENGTH, &infologLength);

   if (infologLength > 0) {
      infoLog = (char *)malloc (infologLength);
      glGetShaderInfoLog (obj, infologLength, &charsWritten, infoLog);
      printf ("%s\n", infoLog);
      free (infoLog);
   }
}

void Glsl::printProgramInfoLog (GLuint obj)
{
   int infologLength = 0;
   int charsWritten = 0;
   char *infoLog;

   glGetProgramiv (obj, GL_INFO_LOG_LENGTH, &infologLength);

   if (infologLength > 0) {
      infoLog = (char *)malloc (infologLength);
      glGetProgramInfoLog (obj, infologLength, &charsWritten, infoLog);
      printf ("%s\n", infoLog);
      free (infoLog);
   }
}
